package com.xiongkf.blockchain.service;

import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.server.WebSocketServer;
import com.alibaba.fastjson.JSON;
import com.xiongkf.blockchain.message.Constant;
import com.xiongkf.blockchain.message.Message;
import com.xiongkf.blockchain.pojo.Block;
import com.xiongkf.blockchain.pojo.BlockChain;

/**
 * P2P服务类
 * 
 * @author bear.xiong
 */
public class P2PService {
	private List<WebSocket> sockets;
	private BlockChain blockChain;

	// p2p服务初始化
	public P2PService(BlockChain blockChain) {
		this.blockChain = blockChain;
		this.sockets = new ArrayList<WebSocket>();
	}

	// websoket服务端
	public void initP2PServer(int port) {
		// webSoketServer初始化
		final WebSocketServer socket = new WebSocketServer(new InetSocketAddress(port)) {
			// 连接打开
			public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
				System.err.println("onOpen webSocket");
				sockets.add(webSocket);
			}

			// 连接关闭
			public void onClose(WebSocket webSocket, int i, String s, boolean b) {
				System.out.println("connection failed to peer: " + webSocket.getRemoteSocketAddress());
				sockets.remove(webSocket);
			}

			// 连接接收消息
			public void onMessage(WebSocket webSocket, String message) {
				handleMessage(webSocket, message);
				System.err.println("onMessage -> " + message);
			}

			// 容错，异常处理
			public void onError(WebSocket webSocket, Exception e) {
				System.out.println("connection failed to peer:" + webSocket.getRemoteSocketAddress());
				sockets.remove(webSocket);
			}

			// 连接开始
			public void onStart() {
				System.err.println("onStart webSocket");
			}
		};

		// soket启动
		socket.start();
		System.out.println("listening websocket p2p port on: " + port);
	}

	// websocket客户端
	public void connectToPeer(String peer) {
		try {
			final WebSocketClient socket = new WebSocketClient(new URI(peer)) {
				// 连接打开
				@Override
				public void onOpen(ServerHandshake serverHandshake) {
					// 查询最新区块
					write(this, queryChainLatestMessage());
					sockets.add(this);
				}

				// 处理服务器端返回的消息
				@Override
				public void onMessage(String message) {
					handleMessage(this, message);
				}

				// 连接关闭
				@Override
				public void onClose(int i, String s, boolean b) {
					System.out.println("connection failed");
					sockets.remove(this);
				}

				// 异常处理
				@Override
				public void onError(Exception e) {
					System.out.println("connection failed");
					sockets.remove(this);
				}
			};

			// 连接成功
			socket.connect();
		} catch (URISyntaxException e) {
			System.out.println("p2p connect is error: " + e.getMessage());
		}
	}

	private void write(WebSocket ws, String message) {
		ws.send(message);
	}

	private String queryChainLatestMessage() {
		return JSON.toJSONString(new Message(Constant.QUERY_LATEST));
	}

	// 消息处理
	private void handleMessage(WebSocket webSocket, String meessageJSON) {
		try {
			// 解析消息体
			Message message = JSON.parseObject(meessageJSON, Message.class);
			System.out.println("Received message: " + JSON.toJSONString(message));
			switch (message.getType()) {
				// 查询最新的区块
				case Constant.QUERY_LATEST:
					write(webSocket, responseLatestMessage());
					break;
				// 查询全部的区块
				case Constant.QUERY_ALL:
					write(webSocket, responseChainMessage());
					break;
				// 返回区块链
				case Constant.QUERY_BLOCKCHAIN:
					handleBlockChainResponse(message.getData());
					break;
			}
		} catch (Exception e) {
			System.out.println("handle message is error: " + e.getMessage());
		}
	}

	// 查询最新的区块
	public String responseLatestMessage() {
		Block[] blocks = { blockChain.getLatestBlock() };
		return JSON.toJSONString(new Message(Constant.QUERY_BLOCKCHAIN, JSON.toJSONString(blocks)));
	}

	// 查询全部的区块
	private String responseChainMessage() {
		return JSON.toJSONString(new Message(Constant.QUERY_BLOCKCHAIN, JSON.toJSONString(blockChain.getBlockChain())));
	}

	// 返回区块链
	private void handleBlockChainResponse(String message) {
		List<Block> receiveBlocks = JSON.parseArray(message, Block.class);

		// 依据索引排序
//		receiveBlocks.sort((b1, b2) ->b1.getIndex().compareTo(b2.getIndex()));
		Collections.sort(receiveBlocks, new Comparator<Block>() {
			public int compare(Block o1, Block o2) {
				return o1.getIndex() - o1.getIndex();
			}
		});

		// peer2返回的一个最新区块的数据
		Block latestBlockReceived = receiveBlocks.get(receiveBlocks.size() - 1);

		// 本节点的最新的区块数据
		Block latestBlock = blockChain.getLatestBlock();

		// 比较请求回来的最新区块，是否大于本地的最新区块的索引值
		if (latestBlockReceived.getIndex() > latestBlock.getIndex()) {
			// 判断本地的最新区块的hash值，是否是请求回来最新区块的前置hash
			if (latestBlock.getHash().equals(latestBlockReceived.getPreviousHash())) {
				System.out.println("We can append the received block to our chain");

				// 把最新区块添加到本地区块
				blockChain.addBlock(latestBlockReceived);

				// 广播出去给其他节点
				broatcast(responseLatestMessage());
			} else if (receiveBlocks.size() == 1) {
				System.out.println("We have to query the chain from our peer");
				// 请求过来的最新区块的长度为1且不符合上一个条件
				broatcast(queryAllMessage());
			} else {
				// 接收整个区块链的数据，并替换本地链，最长链机制的p2p核心逻辑
				blockChain.replaceChain(receiveBlocks);
			}
		} else {
			// 索引较小或相同的情况就不做任何处理
			System.out.println("received blockchain is not longer than received blockchain, Do nothing");
		}
	}

	// 广播消息
	public void broatcast(String message) {
		for (WebSocket socket : sockets) {
			this.write(socket, message);
		}
	}

	private String queryAllMessage() {
		return JSON.toJSONString(new Message(Constant.QUERY_ALL));
	}

	public List<WebSocket> getSockets() {
		return sockets;
	}

	public static void main(String[] args) {
		BlockChain blockChain = new BlockChain();
		P2PService p2p = new P2PService(blockChain);
		// 初始化服务端
		p2p.initP2PServer(8888);

		// 初始化客户端
		p2p.connectToPeer("ws://127.0.0.1:8888");
	}
}
